package net.spadea.math.fraction;

/* Author: Dave Spadea
 * March 2011
 * 
 * GNU LGPL
 * 
 */

import java.math.BigInteger;
import java.util.Random;

public class Fraction extends Number 
					  implements Comparable<Fraction>, Cloneable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -2708813517623593324L;

	private BigInteger whole;
	private BigInteger numerator;
	private BigInteger denominator;
	
	
	public BigInteger getWhole() {
		return whole;
	}

	public void setWhole(BigInteger whole) {
		this.whole = whole;
	}

	public BigInteger getNumerator() {
		return numerator;
	}

	public void setNumerator(BigInteger numerator) {
		this.numerator = numerator;
	}

	public BigInteger getDenominator() {
		return denominator;
	}

	public void setDenominator(BigInteger denominator) {
		this.denominator = denominator;
	}

	
	public synchronized static Fraction generateRandom() {
		
		Fraction frac = new Fraction();
		
		Random seed = new Random();
		Random generator = new Random((int)(seed.nextGaussian() * 1000));
		
		frac.whole = BigInteger.valueOf(generator.nextInt(10));
		frac.numerator = BigInteger.valueOf(generator.nextInt(10) + 1);
		frac.denominator = BigInteger.valueOf(generator.nextInt(10) + 2);
		
		frac.toMixed();
		
		return frac;
		
	}
	
	public Fraction() {
		initFields();
	}
	
	public Fraction(long whole) {
		initFields();
		this.whole = BigInteger.valueOf(whole);
	}
	
	public Fraction(long whole, long numerator, long denominator) {
		this.whole = BigInteger.valueOf(whole);
		this.numerator = BigInteger.valueOf(numerator);
		this.denominator = BigInteger.valueOf(denominator);
	}
	
	private void initFields() {
		whole = BigInteger.valueOf(0);
		numerator = BigInteger.valueOf(0);
		denominator = BigInteger.valueOf(1);
	}
	
	public synchronized void toMixed() {
		//System.out.println("toMixed start");
		toLowestTerms();

		// This logic could be better.
		if (numerator.abs().compareTo(denominator.abs()) >= 0) {
			whole = whole.add(numerator.divide(denominator));
			numerator = numerator.abs().mod(denominator);
		}
		
		if (numerator.compareTo(BigInteger.valueOf(0)) < 0) {
			whole = whole.subtract(BigInteger.valueOf(1));
			numerator = denominator.add(numerator); 
		}
		
		//System.out.println("toMixed end");
	}
	
	public synchronized void toLowestTerms() {
		
		BigInteger gcd = numerator.gcd(denominator);
		
		numerator = numerator.divide(gcd);
		denominator = denominator.divide(gcd);

	}
	
	public synchronized void toImproper() {
		numerator = numerator.add( whole.multiply(denominator) );
		whole = BigInteger.valueOf(0);
	}
	
	public synchronized void toReciprocal() {
		toImproper();
		
		BigInteger temp;
		
		temp = numerator;
		numerator = denominator;
		denominator = temp;
		
		toMixed();
	}
	
	public synchronized Fraction add(Fraction addend) {
		
		Fraction sum = new Fraction();
		Fraction add = addend.clone();
		
		sum.setWhole(this.whole.add(add.getWhole()));
		sum.setNumerator( (this.numerator.multiply(add.getDenominator())).add(
				add.getNumerator().multiply(this.denominator) ));
		sum.setDenominator(this.denominator.multiply(add.getDenominator() ));
		
		sum.toMixed();
		
		return sum;
	}
	
	public synchronized Fraction subtract(Fraction subtrahend) {
		
		Fraction difference = new Fraction();
		Fraction sub = subtrahend.clone();
		
		// Add the negative
		sub.toImproper();
		sub.setNumerator(sub.getNumerator().negate());
		difference = this.add(sub);
		
		return difference;
	}
	
	public synchronized Fraction multiply(Fraction multiplicand) {
		Fraction product = new Fraction();
		Fraction mult = multiplicand.clone();
		
		toImproper();
		mult.toImproper();
		
		product.setNumerator(numerator.multiply(mult.getNumerator()));
		product.setDenominator(denominator.multiply(mult.getDenominator()));
		
		toMixed();
		product.toMixed();
		
		return product; 
	}
	
	public synchronized Fraction divide(Fraction divisor) {
		Fraction quotient;
		Fraction div = divisor.clone();
		
		// Multiply by the reciprocal
		div.toReciprocal();
		quotient = this.multiply(div);
	
		return quotient;
	}

	public synchronized int compareTo(Fraction other) {
		
		if(other == null) {
			throw new NullPointerException();
		}
		
		toImproper();
		other.toImproper();
		
		BigInteger cp1 = this.numerator.multiply(other.getDenominator());
		BigInteger cp2 = this.denominator.multiply(other.getNumerator());
		
		toMixed();
		other.toMixed();
		
		return cp1.compareTo(cp2);
		
	}
	
	public synchronized boolean equals(Fraction other) {

		if(other == null) { return false; }
		
		if(compareTo(other) == 0) { return true; } else { return false; }
	}
	
	@Override
	public synchronized Fraction clone() {
		Fraction miniMe = new Fraction();
		
		miniMe.setWhole(getWhole());
		miniMe.setNumerator(getNumerator());
		miniMe.setDenominator(getDenominator());
		
		return miniMe;
	}

	@Override
	public synchronized double doubleValue() {
		toImproper();
		double val = numerator.doubleValue() / denominator.doubleValue();
		toMixed();
		
		return val;
	}

	@Override
	public synchronized float floatValue() {
		toImproper();
		float val = numerator.floatValue() / (float)denominator.floatValue();
		toMixed();
		
		return val;
	}

	@Override
	public int intValue() {
		toMixed();
		return whole.intValue();
	}

	@Override
	public long longValue() {
		toMixed();
		return whole.longValue();
	}
	
	public synchronized String toString() {
		
		StringBuilder sb = new StringBuilder();
		
		if (this.whole.intValue() != 0 || (this.numerator.intValue() == 0)) {
			sb.append(this.whole + " ");	
		}
		
		
		if(this.numerator.intValue() != 0) {
			sb.append(this.numerator + "/" + this.denominator);
		}
		
		return sb.toString();
		
	}

}
